package com.example.basic.web.service.redis.impl;


import com.example.basic.utils.JsonUtils;
import com.example.basic.web.service.redis.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @author vic
 * @desc resdis service
 *
 */
@Service
public class RedisServiceImpl implements IRedisService {

	@Autowired
	private RedisTemplate<String, ?> redisTemplate;

	@Value("${spring.distributedLock.timeout:5000}")
	private long maxLockTimeOut;

	private static final String LOCK_SUCCESS = "OK";
	private static final String SET_IF_NOT_EXIST = "NX";
	private static final String SET_WITH_EXPIRE_TIME = "PX";

    public static final Long RELEASE_SUCCESS = 1L;
	public static final Long NO_LOCKED = 0L;
	
	@Override
	public boolean set(final String key, final String value) {
		boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.set(serializer.serialize(key), serializer.serialize(value));
                return true;
            }
	    });
		return result;
	}

	@Override
	public String get(final String key){
		String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] value =  connection.get(serializer.serialize(key));
                if(value == null){
                	return null;
				}
                return serializer.deserialize(value);
            }
	    });
		return result;
	}

	@Override
	public boolean expire(final String key, long expire) {
		return redisTemplate.expire(key, expire, TimeUnit.DAYS);
	}

	@Override
	public <T> boolean setList(String key, List<T> list) {
		String value = JsonUtils.toJsonString(list);
		return set(key,value);
	}

	@Override
	public <T> List<T> getList(String key,Class<T> clz){
		String json = get(key);
		if(json!=null){
			List<T> list = JsonUtils.toBeanList(json, clz);
			return list;
		}
		return null;
	}

	@Override
	public boolean setMap(String key, Map<Object,Object> map){
		if(null != map)
		{
			HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
			for (Map.Entry<Object, Object> entry : map.entrySet()) {
				/*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  */
				if(hashOperations != null){
					hashOperations.put(key,entry.getKey(),entry.getValue());
				} else{
					return false;
				}
			}
		} else{
			return false;
		}
		return true;
	}

	@Override
	public Map<Object,Object> getMap(String key){
		/*HashOperations<String, Object, Object> hashOperation = redisTemplate.opsForHash();*/
		Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
		/*Map<String, T> map = hashOperation.entries(key);*/
		return map;
	}

	@Override
	public long lpush(final String key, Object obj) {
		final String value = JsonUtils.toJsonString(obj);
		long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.lPush(serializer.serialize(key), serializer.serialize(value));
                return count;
            }
	    });
		return result;
	}

	@Override
	public long rpush(final String key, Object obj) {
		final String value = JsonUtils.toJsonString(obj);
		long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.rPush(serializer.serialize(key), serializer.serialize(value));
                return count;
            }
	    });
		return result;
	}

	@Override
	public String lpop(final String key) {
		String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] res =  connection.lPop(serializer.serialize(key));
				if(res == null){
					return null;
				}
                return serializer.deserialize(res);
            }
	    });
		return result;
	}

	@Override
	public long hlen(String key) {
		long result = redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				long count = connection.hLen(serializer.serialize(key));
				return count;
			}
		});
		return result;
	}

	@Override
	public String hget(String key, String filed) {
		String result = redisTemplate.execute(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				byte[] res =  connection.hGet(serializer.serialize(key),serializer.serialize(filed));
				if(res == null){
					return null;
				}
				return serializer.deserialize(res);
			}
		});
		return result;
	}

	@Override
	public List<String> hMGetList(String key, byte[]... fields) {
		RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
		List<String> result = redisTemplate.execute(new RedisCallback<List<String>>() {
			@Override
			public List<String> doInRedis(RedisConnection connection) throws DataAccessException {
				List<String> data = new ArrayList<>();
				List<byte[]> list = connection.hMGet(key.getBytes(), fields);
				for (int i=0; i<fields.length; i++) {
					data.add(i, serializer.deserialize(list.get(i)));
				}
				return data;
			}
		});
		return result;
	}

	@Override
	public Map<String, String> hMGetMap(String key, byte[]... fields) {
		RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
		Map<String, String> result = redisTemplate.execute(new RedisCallback<Map<String, String>>() {
			@Override
			public Map<String, String> doInRedis(RedisConnection connection) throws DataAccessException {
				Map<String,String> data = new HashMap<String,String>(8);
				List<byte[]> list = connection.hMGet(key.getBytes(), fields);
				for (int i=0; i<fields.length; i++) {
					data.put(serializer.deserialize(fields[i]), serializer.deserialize(list.get(i)));
				}
				return data;
			}
		});
		return result;
	}

	@Override
	public Map<String, String> hgetAll(String key) {
		Map<String, String> result = redisTemplate.execute(new RedisCallback<Map<String, String>>() {
			@Override
			public Map<String, String> doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				Map<byte[], byte[]> res =  connection.hGetAll(serializer.serialize(key));
				if(res == null){
					return null;
				}
				Map<String,String> data = new HashMap<String,String>(8);
				for(byte[] key : res.keySet()){
					data.put(serializer.deserialize(key),serializer.deserialize(res.get(key)));
				}
				return data;
			}
		});
		return result;
	}

	@Override
	public Boolean hset(String key, String filed,String value) {
		return redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				return  connection.hSet(serializer.serialize(key),serializer.serialize(filed),serializer.serialize(value));
			}
		});
	}

	@Override
	public void batchHset(String key, List<Map<String, String>> list) {
		redisTemplate.executePipelined(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				for (int i = 0; i < list.size(); i++) {
					connection.hSet(key.getBytes(), list.get(i).get("field").getBytes(), list.get(i).get("value").getBytes());
				}
				return null;
			}
		});
	}

	@Override
	public Boolean hdel(String key, String filed) {
		return redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				Long result =   connection.hDel(serializer.serialize(key),serializer.serialize(filed));
				if(result == null || result== 0){
					return false;
				}else {
					return true;
				}
			}
		});
	}


	@Override
	public Set<String> hkeys(String key) {
		return redisTemplate.execute(new RedisCallback<Set<String>>() {
			@Override
			public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				Set<byte[]> res =  connection.hKeys(serializer.serialize(key));
				if(res == null){
					return Collections.emptySet();
				}
				Set<String> re = new HashSet<>();
				if(res.size()>0){
					for(byte[] s : res){
						re.add(serializer.deserialize(s));
					}
				}
				return re;
			}
		});
	}

	@Override
	public Set<String> sMembers(String key) {
		Set<String> result = redisTemplate.execute(new RedisCallback<Set<String>>() {
			@Override
			public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				Set<byte[]> res =  connection.sMembers(serializer.serialize(key));
				if(res == null){
					return Collections.emptySet();
				}
				Set<String> re = new HashSet<>();
				if(res.size()>0){
					for(byte[] s : res){
						re.add(serializer.deserialize(s));
					}
				}
				return re;
			}
		});
		return result;
	}

	@Override
	public Boolean sIsMember(String key, String value) {
		return redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				return  connection.sIsMember(serializer.serialize(key),serializer.serialize(value));
			}
		});
	}

	@Override
	public Long sadd(String key, String value) {
		return redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				return  connection.sAdd(serializer.serialize(key),serializer.serialize(value));
			}
		});
	}

	@Override
	public Long srem(String key, String value) {
		return redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				return  connection.sRem(serializer.serialize(key),serializer.serialize(value));
			}
		});
	}

	@Override
	public boolean del(String key) {
		boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.del(serializer.serialize(key));
                return true;
            }
	    });
		return result;
	}

	/**
	 * 根据前参数模糊查询keys（*：通配任意多个字符  ?：通配单个字符    []：通配括号内的某一个字符）
	 * @param beforePattern
	 * @return
	 */
	@Override
	public Set<String> getKeysByBeforePattern(String beforePattern){
		//这里由于目前只用到了根据前面的参数模糊匹配，后续如果有相关需求可再拓展，
		return redisTemplate.keys(beforePattern + "*");
		// return stringRedisTemplate.keys("?" + pattern);
		// return stringRedisTemplate.keys("[" + pattern + "]");
	}

	@Override
	public boolean getDistributedLock(String lockKey, int expireTime) throws Exception {
		boolean result = getLockResult(lockKey, "ruiyi", expireTime);
		if(!result) {
			long start = System.currentTimeMillis();
			while (!result) {
				result = getLockResult(lockKey, "ruiyi", expireTime);
				long end = System.currentTimeMillis();
				if((end-start) > maxLockTimeOut){
					throw new Exception("事务繁忙，请稍后再试。");
				}
			}
		}
		return result;
	}

	public boolean getLockResult(String lockKey, String requestId, int expireTime){
		boolean result = false;
		String lockResult = redisTemplate.execute(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				Jedis jedis = (Jedis) connection.getNativeConnection();
				return jedis.set(lockKey, requestId);
			}
		});
		if (LOCK_SUCCESS.equals(lockResult)) {
			result = true;
		}
		return result;
	}

	@Override
	public boolean releaseDistributedLock(String lockKey) {
		return redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				boolean result = false;
				Jedis jedis = (Jedis) connection.getNativeConnection();
				String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
				Object releaseResult = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList("ruiyi"));
				if (RELEASE_SUCCESS.equals(releaseResult) || NO_LOCKED.equals(releaseResult)) {
					result = true;
				}
				return result;
			}
		});
	}

	@Override
	public int incrBy(String key, long incTimes) {
		String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
            	RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long value =  connection.incrBy(serializer.serialize(key), incTimes);
                
                return String.valueOf(value);
            }
	    });
		return Integer.valueOf(result).intValue();
	}

	private void checkRedisDbId(Integer dbId) throws Exception {
		if(dbId!=null && (dbId > 15 || dbId < 0)){
			throw new Exception("redis's dbId is between 0 and 15");
		}
	}

	@Override
	public Object eval(String script) {
		return redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				Jedis jedis = (Jedis) connection.getNativeConnection();
				return jedis.eval(script);
			}
		});
	}

	@Override
	public boolean expire(final String key, long expire,TimeUnit timeUnit) {
		return redisTemplate.expire(key, expire, timeUnit);
	}

	@Override
	public Boolean setKey(String flag, String key, String value) {
		String redisKey = flag+key;
		boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				connection.set(serializer.serialize(redisKey), serializer.serialize(value));
				return true;
			}
		});
		return result;
	}

	@Override
	public String getRedis(String key) {
		String result = redisTemplate.execute(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				byte[] value =  connection.get(serializer.serialize(key));
				if(value == null){
					return null;
				}
				return serializer.deserialize(value);
			}
		});
		return result;
	}

	@Override
	public String rpop(String FILE_DELETE_LIST) {
		String result = redisTemplate.execute(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
				byte[] value =  connection.rPop(serializer.serialize(FILE_DELETE_LIST));

				return String.valueOf(value);
			}
		});
		return result;
	}

	@Override
	public Long hIncrby(String key, String hKey, long delta){
		return redisTemplate.opsForHash().increment(key, hKey, delta);
	}

	@Override
	public Boolean hSetnx(String key, Object hKey, Object hValue){
		return redisTemplate.opsForHash().putIfAbsent(key, hKey, hValue);
	}
}

